﻿using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.Routing;
using PMS.Core.Caching;
using PMS.Data.Entities.Customers;
using PMS.Data.Entities.Messages;
using PMS.Data.Entities.Settings;
using PMS.Data.Entities.Stores;
using PMS.Services.Caching;
using PMS.Services.EntityServices.Common;
using PMS.Services.EntityServices.Customers;
using PMS.Services.EntityServices.Stores;
using PMS.Services.SharedKernel;
using PMS.Services.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;

namespace PMS.Services.EntityServices.Messages
{
    /// <summary>
    /// Message token provider
    /// </summary>
    public partial class EmailMessageTemplateTokenProvider : IEmailMessageTemplateTokenProvider
    {
        #region Fields

        private readonly CustomerSettings _customerSettings;
        private readonly IWorkContext _workContext;
        private readonly IStoreContext _storeContext;
        private readonly IStoreService _storeService;
        private readonly IDateTimeHelper _dateTimeHelper;
        private readonly ICustomerService _customerService;
        private readonly IUrlHelperFactory _urlHelperFactory;
        private readonly ICacheKeyService _cacheKeyService;
        private readonly IStaticCacheManager _staticCacheManager;
        private readonly IActionContextAccessor _actionContextAccessor;
        private readonly IGenericAttributeService _genericAttributeService;

        private Dictionary<string, IEnumerable<string>> _allowedTokens;

        #endregion

        #region Ctor

        public EmailMessageTemplateTokenProvider(CustomerSettings customerSettings,
            IWorkContext workContext,
            IStoreContext storeContext,
            IStoreService storeService,
            IDateTimeHelper dateTimeHelper,
            ICustomerService customerService,
            IUrlHelperFactory urlHelperFactory,
            ICacheKeyService cacheKeyService,
            IStaticCacheManager cacheManager,
            IActionContextAccessor actionContextAccessor,
            IGenericAttributeService genericAttributeService)
        {
            _customerSettings = customerSettings;
            _workContext = workContext;
            _storeContext = storeContext;
            _storeService = storeService;
            _dateTimeHelper = dateTimeHelper;
            _customerService = customerService;
            _urlHelperFactory = urlHelperFactory;
            _cacheKeyService = cacheKeyService;
            _staticCacheManager = cacheManager;
            _actionContextAccessor = actionContextAccessor;
            _genericAttributeService = genericAttributeService;
        }

        #endregion

        #region Allowed tokens

        /// <summary>
        /// Get all available tokens by token groups
        /// </summary>
        protected Dictionary<string, IEnumerable<string>> AllowedTokens
        {
            get
            {
                if (_allowedTokens != null)
                    return _allowedTokens;

                _allowedTokens = new Dictionary<string, IEnumerable<string>>();

                //store tokens
                _allowedTokens.Add(EmailTokenGroupNames.StoreTokens, new[]
                {
                    "%Store.Name%",
                    "%Store.URL%",
                    "%Store.Email%",
                    "%Store.CompanyName%",
                    "%Store.CompanyAddress%",
                    "%Store.CompanyPhoneNumber%",
                });

                //customer tokens
                _allowedTokens.Add(EmailTokenGroupNames.CustomerTokens, new[]
                {
                    "%Customer.Email%",
                    "%Customer.Username%",
                    "%Customer.Mobile%",
                    "%Customer.FullName%",  //Nickname
                    "%Customer.IsOutputPassword%",
                    "%Customer.Password%",
                    "%Customer.CustomAttributes%",
                    "%Customer.PasswordRecoveryURL%",
                    "%Customer.AccountActivationURL%",
                    "%Customer.ChangeEmailVerifyCode%"
                });

               
                return _allowedTokens;
            }
        }

        #endregion

        #region Utilities

        /// <summary>
        /// Generates an absolute URL for the specified store, routeName and route values
        /// </summary>
        /// <param name="storeId">Store identifier; Pass 0 to load URL of the current store</param>
        /// <param name="routeName">The name of the route that is used to generate URL</param>
        /// <param name="routeValues">An object that contains route values</param>
        /// <returns>Generated URL</returns>
        protected virtual string RouteUrl(int storeId = 0, string routeName = null, object routeValues = null)
        {
            //try to get a store by the passed identifier
            var store = _storeService.GetStoreById(storeId) ?? _storeContext.CurrentStore
                ?? throw new Exception("No store could be loaded");

            //ensure that the store URL is specified
            if (string.IsNullOrEmpty(store.Url))
                throw new Exception("URL cannot be null");

            //generate a URL with an absolute path
            var urlHelper = _urlHelperFactory.GetUrlHelper(_actionContextAccessor.ActionContext);
            var url = new PathString(urlHelper.RouteUrl(routeName, routeValues));

            //remove the application path from the generated URL if exists
            var pathBase = _actionContextAccessor.ActionContext?.HttpContext?.Request?.PathBase ?? PathString.Empty;
            url.StartsWithSegments(pathBase, out url);

            //compose the result
            return Uri.EscapeUriString(WebUtility.UrlDecode($"{store.Url.TrimEnd('/')}{url}"));
        }

        #endregion

        #region Methods

        /// <summary>
        /// Add store tokens
        /// </summary>
        /// <param name="tokens">List of already added tokens</param>
        /// <param name="store">Store</param>
        /// <param name="emailAccount">Email account</param>
        public virtual void AddStoreTokens(IList<EmailMesssageTemplateToken> tokens, Store store, EmailAccount emailAccount)
        {
            if (emailAccount == null)
                throw new ArgumentNullException(nameof(emailAccount));

            tokens.Add(new EmailMesssageTemplateToken("Store.Id", store.Id));
            tokens.Add(new EmailMesssageTemplateToken("Store.Name", store.Name));
            tokens.Add(new EmailMesssageTemplateToken("Store.URL", store.Url, true));
            tokens.Add(new EmailMesssageTemplateToken("Store.Email", emailAccount.Email));
            tokens.Add(new EmailMesssageTemplateToken("Store.CompanyName", store.CompanyName));
            tokens.Add(new EmailMesssageTemplateToken("Store.CompanyAddress", store.CompanyAddress));
            tokens.Add(new EmailMesssageTemplateToken("Store.CompanyPhoneNumber", store.CompanyPhoneNumber));
            
        }

        /// <summary>
        /// Add customer tokens
        /// </summary>
        /// <param name="tokens">List of already added tokens</param>
        /// <param name="customerId">Customer identifier</param>
        /// <param name="customerPwd">CustomerPassword output to Email if needed</param>
        public virtual void AddCustomerTokens(IList<EmailMesssageTemplateToken> tokens, int customerId, string customerPwd = null)
        {
            if (customerId <= 0)
                throw new ArgumentOutOfRangeException(nameof(customerId));

            var customer = _customerService.GetCustomerById(customerId);

            AddCustomerTokens(tokens, customer, customerPwd);
        }

        /// <summary>
        /// Add customer tokens
        /// </summary>
        /// <param name="tokens">List of already added tokens</param>
        /// <param name="customer">Customer</param>
        /// <param name="customerPwd">CustomerPassword output to Email if needed</param>
        public virtual void AddCustomerTokens(IList<EmailMesssageTemplateToken> tokens, Customer customer, string customerPwd = null)
        {
            tokens.Add(new EmailMesssageTemplateToken("Customer.Id", customer.Id));
            tokens.Add(new EmailMesssageTemplateToken("Customer.Email", customer.Email));
            tokens.Add(new EmailMesssageTemplateToken("Customer.Username", customer.LoginName));
            tokens.Add(new EmailMesssageTemplateToken("Customer.FullName", customer.NickName));
            tokens.Add(new EmailMesssageTemplateToken("Customer.Mobile", customer.Mobile));
            tokens.Add(new EmailMesssageTemplateToken("Customer.IsOutputPassword", !string.IsNullOrEmpty(customerPwd)));
            tokens.Add(new EmailMesssageTemplateToken("Customer.Password", string.IsNullOrEmpty(customerPwd) ? "" : customerPwd));
            //var customAttributesXml = _genericAttributeService.GetAttribute<string>(customer, NopCustomerDefaults.CustomCustomerAttributes);
            //tokens.Add(new EmailMesssageTemplateToken("Customer.CustomAttributes", _customerAttributeFormatter.FormatAttributes(customAttributesXml), true));

            //note: we do not use SEO friendly URLS for these links because we can get errors caused by having .(dot) in the URL (from the email address)
            var passwordRecoveryUrl = RouteUrl(routeName: "PasswordRecoveryConfirmView", routeValues: new { token = _genericAttributeService.GetAttribute<string>(customer, CustomerDefaultConfigs.PasswordRecoveryTokenAttribute), guid = customer.CustomerGuid });
            var accountActivationUrl = RouteUrl(routeName: "AccountActivationView", routeValues: new { token = _genericAttributeService.GetAttribute<string>(customer, CustomerDefaultConfigs.AccountActivationTokenAttribute), guid = customer.CustomerGuid });

            tokens.Add(new EmailMesssageTemplateToken("Customer.IsPwdRecoverUrlExpired", _customerSettings.PasswordRecoveryLinkDaysValid > 0, true));
            tokens.Add(new EmailMesssageTemplateToken("Customer.RecoverUrlValidDays", _customerSettings.PasswordRecoveryLinkDaysValid, true));
            tokens.Add(new EmailMesssageTemplateToken("Customer.PasswordRecoveryURL", passwordRecoveryUrl, true));
            tokens.Add(new EmailMesssageTemplateToken("Customer.AccountActivationURL", accountActivationUrl, true));
            
            var verifyCodeKey = _cacheKeyService.PrepareKeyForDefaultCache(CacheKeyConfigs.CustomerEmailVerifyCodeCacheKey, EmailMessageTemplateNames.CustomerSendChangeEmailAccountCodeMessage, customer.Id);
            if (_staticCacheManager.IsSet(verifyCodeKey))
            {
                var verifyCode = _staticCacheManager.Get<string>(verifyCodeKey, () => null);
                tokens.Add(new EmailMesssageTemplateToken("Customer.ChangeEmailVerifyCode", verifyCode));
            }
        }

        /// <summary>
        /// Add private message tokens
        /// </summary>
        /// <param name="tokens">List of already added tokens</param>
        /// <param name="privateMessage">Private message</param>
        //public virtual void AddPrivateMessageTokens(IList<EmailMesssageTemplateToken> tokens, PrivateMessage privateMessage)
        //{
        //    //attributes
        //    //we cannot inject IForumService into constructor because it'll cause circular references.
        //    //that's why we resolve it here this way
        //    var forumService = EngineContext.Current.Resolve<IForumService>();

        //    tokens.Add(new EmailMesssageTemplateToken("PrivateMessage.Subject", privateMessage.Subject));
        //    tokens.Add(new EmailMesssageTemplateToken("PrivateMessage.Text", forumService.FormatPrivateMessageText(privateMessage), true));

        //    //event notification
        //    _eventPublisher.EntityTokensAdded(privateMessage, tokens);
        //}

         
        /// <summary>
        /// Get collection of allowed (supported) message tokens
        /// </summary>
        /// <param name="tokenGroups">Collection of token groups; pass null to get all available tokens</param>
        /// <returns>Collection of allowed message tokens</returns>
        public virtual IEnumerable<string> GetListOfAllowedTokens(IEnumerable<string> tokenGroups = null)
        {
            var allowedTokens = AllowedTokens.Where(x => tokenGroups == null || tokenGroups.Contains(x.Key))
                .SelectMany(x => x.Value).ToList();

            return allowedTokens.Distinct();
        }

        /// <summary>
        /// Get token groups of message template
        /// </summary>
        /// <param name="messageTemplate">Message template</param>
        /// <returns>Collection of token group names</returns>
        public virtual IEnumerable<string> GetTokenGroups(EmailMessageTemplate messageTemplate)
        {
            //groups depend on which tokens are added at the appropriate methods in IWorkflowMessageService
            switch (messageTemplate.TemplateName)
            {
                case EmailMessageTemplateNames.CustomerWelcomeMessageAfterRegister:
                case EmailMessageTemplateNames.CustomerEmailValidationMessage:
                case EmailMessageTemplateNames.CustomerPasswordRecoveryMessage:
                case EmailMessageTemplateNames.CustomerPasswordChangedMessage:
                case EmailMessageTemplateNames.CustomerBirthdayGreetingMessage:
                    return new[] { EmailTokenGroupNames.StoreTokens, EmailTokenGroupNames.CustomerTokens };

                default:
                    return Array.Empty<string>();
            }
        }

        #endregion
    }
}
